package com.polidea.rxandroidble.mockrxandroidble;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.support.annotation.NonNull;
import com.polidea.rxandroidble.NotificationSetupMode;
import com.polidea.rxandroidble.RxBleConnection;
import com.polidea.rxandroidble.RxBleDeviceServices;
import com.polidea.rxandroidble.RxBleRadioOperationCustom;
import com.polidea.rxandroidble.exceptions.BleConflictingNotificationAlreadySetException;
import com.polidea.rxandroidble.internal.connection.ImmediateSerializedBatchAckStrategy;
import com.polidea.rxandroidble.internal.util.ObservableUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import rx.Completable;
import rx.Observable;
import rx.functions.Action0;
import rx.functions.Actions;
import rx.functions.Func1;
import static rx.Observable.just;
public class RxBleConnectionMock implements RxBleConnection {
/**
* Value used to enable notification for a client configuration descriptor
*/
private static final byte[] ENABLE_NOTIFICATION_VALUE = {0x01, 0x00};
/**
* Value used to enable indication for a client configuration descriptor
*/
private static final byte[] ENABLE_INDICATION_VALUE = {0x02, 0x00};
/**
* Value used to disable notifications or indicatinos
*/
private static final byte[] DISABLE_NOTIFICATION_VALUE = {0x00, 0x00};
private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private HashMap<UUID, Observable<Observable<byte[]>>> notificationObservableMap = new HashMap<>();
private HashMap<UUID, Observable<Observable<byte[]>>> indicationObservableMap = new HashMap<>();
private RxBleDeviceServices rxBleDeviceServices;
private int rssi;
private int currentMtu = 23;
private Map<UUID, Observable<byte[]>> characteristicNotificationSources;
public RxBleConnectionMock(RxBleDeviceServices rxBleDeviceServices,
int rssi,
Map<UUID, Observable<byte[]>> characteristicNotificationSources) {
this.rxBleDeviceServices = rxBleDeviceServices;
this.rssi = rssi;
this.characteristicNotificationSources = characteristicNotificationSources;
}
@Override
public Observable<Integer> requestMtu(final int mtu) {
return Observable.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
currentMtu = mtu;
return mtu;
}
});
}
@Override
public int getMtu() {
return currentMtu;
}
@Override
public Observable<RxBleDeviceServices> discoverServices() {
return Observable.just(rxBleDeviceServices);
}
@Override
public Observable<RxBleDeviceServices> discoverServices(long timeout, TimeUnit timeUnit) {
return Observable.just(rxBleDeviceServices);
}
@Override
public Observable<BluetoothGattCharacteristic> getCharacteristic(@NonNull final UUID characteristicUuid) {
return discoverServices()
.flatMap(new Func1<RxBleDeviceServices, Observable<? extends BluetoothGattCharacteristic>>() {
@Override
public Observable<? extends BluetoothGattCharacteristic> call(RxBleDeviceServices rxBleDeviceServices) {
return rxBleDeviceServices.getCharacteristic(characteristicUuid);
}
});
}
@Override
public Observable<byte[]> readCharacteristic(@NonNull UUID characteristicUuid) {
return getCharacteristic(characteristicUuid).map(new Func1<BluetoothGattCharacteristic, byte[]>() {
@Override
public byte[] call(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
return bluetoothGattCharacteristic.getValue();
}
});
}
@Override
public Observable<byte[]> readCharacteristic(@NonNull BluetoothGattCharacteristic characteristic) {
return Observable.just(characteristic.getValue());
}
@Override
public Observable<byte[]> readDescriptor(final UUID serviceUuid, final UUID characteristicUuid, final UUID descriptorUuid) {
return discoverServices()
.flatMap(new Func1<RxBleDeviceServices, Observable<BluetoothGattDescriptor>>() {
@Override
public Observable<BluetoothGattDescriptor> call(RxBleDeviceServices rxBleDeviceServices) {
return rxBleDeviceServices.getDescriptor(serviceUuid, characteristicUuid, descriptorUuid);
}
})
.map(new Func1<BluetoothGattDescriptor, byte[]>() {
@Override
public byte[] call(BluetoothGattDescriptor bluetoothGattDescriptor) {
return bluetoothGattDescriptor.getValue();
}
});
}
@Override
public Observable<byte[]> readDescriptor(BluetoothGattDescriptor descriptor) {
return Observable.just(descriptor.getValue());
}
@Override
public Observable<Integer> readRssi() {
return Observable.just(rssi);
}
@Override
public Observable<Observable<byte[]>> setupNotification(@NonNull UUID characteristicUuid) {
return setupNotification(characteristicUuid, NotificationSetupMode.DEFAULT);
}
@Override
public Observable<Observable<byte[]>> setupNotification(@NonNull BluetoothGattCharacteristic characteristic) {
return setupNotification(characteristic, NotificationSetupMode.DEFAULT);
}
@Override
public Observable<Observable<byte[]>> setupNotification(@NonNull final UUID characteristicUuid, final NotificationSetupMode setupMode) {
if (indicationObservableMap.containsKey(characteristicUuid)) {
return Observable.error(new BleConflictingNotificationAlreadySetException(characteristicUuid, true));
}
Observable<Observable<byte[]>> availableObservable = notificationObservableMap.get(characteristicUuid);
if (availableObservable != null) {
return availableObservable;
}
Observable<Observable<byte[]>> newObservable = createCharacteristicNotificationObservable(characteristicUuid, setupMode, false)
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
dismissCharacteristicNotification(characteristicUuid, setupMode, false);
}
})
.map(new Func1<Observable<byte[]>, Observable<byte[]>>() {
@Override
public Observable<byte[]> call(Observable<byte[]> notificationDescriptorData) {
return observeOnCharacteristicChangeCallbacks(characteristicUuid);
}
})
.replay(1)
.refCount();
notificationObservableMap.put(characteristicUuid, newObservable);
return newObservable;
}
@Override
public Observable<Observable<byte[]>> setupNotification(@NonNull BluetoothGattCharacteristic characteristic,
NotificationSetupMode setupMode) {
return setupNotification(characteristic.getUuid(), setupMode);
}
@Override
public Observable<Observable<byte[]>> setupIndication(@NonNull UUID characteristicUuid) {
return setupIndication(characteristicUuid, NotificationSetupMode.DEFAULT);
}
@Override
public Observable<Observable<byte[]>> setupIndication(@NonNull BluetoothGattCharacteristic characteristic) {
return setupIndication(characteristic.getUuid(), NotificationSetupMode.DEFAULT);
}
@Override
public Observable<Observable<byte[]>> setupIndication(@NonNull final UUID characteristicUuid,
@NonNull final NotificationSetupMode setupMode) {
if (notificationObservableMap.containsKey(characteristicUuid)) {
return Observable.error(new BleConflictingNotificationAlreadySetException(characteristicUuid, false));
}
Observable<Observable<byte[]>> availableObservable = indicationObservableMap.get(characteristicUuid);
if (availableObservable != null) {
return availableObservable;
}
Observable<Observable<byte[]>> newObservable = createCharacteristicNotificationObservable(characteristicUuid, setupMode, true)
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
dismissCharacteristicNotification(characteristicUuid, setupMode, true);
}
})
.map(new Func1<Observable<byte[]>, Observable<byte[]>>() {
@Override
public Observable<byte[]> call(Observable<byte[]> notificationDescriptorData) {
return observeOnCharacteristicChangeCallbacks(characteristicUuid);
}
})
.replay(1)
.refCount();
indicationObservableMap.put(characteristicUuid, newObservable);
return newObservable;
}
@Override
public Observable<Observable<byte[]>> setupIndication(@NonNull BluetoothGattCharacteristic characteristic,
@NonNull NotificationSetupMode setupMode) {
return setupIndication(characteristic.getUuid(), setupMode);
}
@Override
public Observable<BluetoothGattCharacteristic> writeCharacteristic(
@NonNull final BluetoothGattCharacteristic bluetoothGattCharacteristic) {
return getCharacteristic(bluetoothGattCharacteristic.getUuid())
.map(new Func1<BluetoothGattCharacteristic, Boolean>() {
@Override
public Boolean call(BluetoothGattCharacteristic characteristic) {
return characteristic.setValue(bluetoothGattCharacteristic.getValue());
}
})
.flatMap(new Func1<Boolean, Observable<? extends BluetoothGattCharacteristic>>() {
@Override
public Observable<? extends BluetoothGattCharacteristic> call(Boolean ignored) {
return Observable.just(bluetoothGattCharacteristic);
}
});
}
@Override
public Observable<byte[]> writeCharacteristic(@NonNull BluetoothGattCharacteristic bluetoothGattCharacteristic, @NonNull byte[] data) {
bluetoothGattCharacteristic.setValue(data);
return Observable.just(data);
}
@Override
public LongWriteOperationBuilder createNewLongWriteBuilder() {
return new LongWriteOperationBuilder() {
private Observable<BluetoothGattCharacteristic> bluetoothGattCharacteristicObservable;
private int maxBatchSize = 20; // default
private byte[] bytes;
private WriteOperationAckStrategy writeOperationAckStrategy = // default
new ImmediateSerializedBatchAckStrategy();
@Override
public LongWriteOperationBuilder setBytes(byte[] bytes) {
this.bytes = bytes;
return this;
}
@Override
public LongWriteOperationBuilder setCharacteristicUuid(final UUID uuid) {
bluetoothGattCharacteristicObservable = discoverServices().flatMap(
new Func1<RxBleDeviceServices, Observable<BluetoothGattCharacteristic>>() {
@Override
public Observable<BluetoothGattCharacteristic> call(RxBleDeviceServices rxBleDeviceServices) {
return rxBleDeviceServices.getCharacteristic(uuid);
}
}
);
return this;
}
@Override
public LongWriteOperationBuilder setCharacteristic(
BluetoothGattCharacteristic bluetoothGattCharacteristic) {
bluetoothGattCharacteristicObservable = Observable.just(bluetoothGattCharacteristic);
return this;
}
@Override
public LongWriteOperationBuilder setMaxBatchSize(int maxBatchSize) {
this.maxBatchSize = maxBatchSize;
return this;
}
@Override
public LongWriteOperationBuilder setWriteOperationAckStrategy(WriteOperationAckStrategy writeOperationAckStrategy) {
this.writeOperationAckStrategy = writeOperationAckStrategy;
return this;
}
@Override
public Observable<byte[]> build() {
if (bluetoothGattCharacteristicObservable == null) {
throw new IllegalArgumentException("setCharacteristicUuid() or setCharacteristic() needs to be called before build()");
}
if (bytes == null) {
throw new IllegalArgumentException("setBytes() needs to be called before build()");
}
final boolean excess = bytes.length % maxBatchSize > 0;
final AtomicInteger numberOfBatches = new AtomicInteger(bytes.length / maxBatchSize + (excess ? 1 : 0));
return Observable
.fromCallable(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return numberOfBatches.get() > 0;
}
})
.compose(writeOperationAckStrategy)
.repeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {
@Override
public Observable<?> call(Observable<? extends Void> onWriteFinished) {
return onWriteFinished
.takeWhile(new Func1<Void, Boolean>() {
@Override
public Boolean call(Void aVoid) {
return numberOfBatches.decrementAndGet() > 0;
}
});
}
})
.toCompletable()
.andThen(Observable.just(bytes));
}
};
}
@Override
public Observable<byte[]> writeCharacteristic(@NonNull UUID characteristicUuid, @NonNull final byte[] data) {
return getCharacteristic(characteristicUuid)
.map(new Func1<BluetoothGattCharacteristic, Boolean>() {
@Override
public Boolean call(BluetoothGattCharacteristic characteristic) {
return characteristic.setValue(data);
}
})
.flatMap(new Func1<Boolean, Observable<? extends byte[]>>() {
@Override
public Observable<? extends byte[]> call(Boolean ignored) {
return Observable.just(data);
}
});
}
@Override
public Observable<byte[]> writeDescriptor(final UUID serviceUuid, final UUID characteristicUuid,
final UUID descriptorUuid, final byte[] data) {
return discoverServices()
.flatMap(new Func1<RxBleDeviceServices, Observable<BluetoothGattDescriptor>>() {
@Override
public Observable<BluetoothGattDescriptor> call(RxBleDeviceServices rxBleDeviceServices) {
return rxBleDeviceServices.getDescriptor(serviceUuid, characteristicUuid, descriptorUuid);
}
})
.map(new Func1<BluetoothGattDescriptor, Boolean>() {
@Override
public Boolean call(BluetoothGattDescriptor bluetoothGattDescriptor) {
return bluetoothGattDescriptor.setValue(data);
}
}).flatMap(new Func1<Boolean, Observable<? extends byte[]>>() {
@Override
public Observable<? extends byte[]> call(Boolean ignored) {
return Observable.just(data);
}
});
}
@Override
public Observable<byte[]> writeDescriptor(final BluetoothGattDescriptor descriptor, final byte[] data) {
return Completable.fromAction(new Action0() {
@Override
public void call() {
descriptor.setValue(data);
}
})
.andThen(Observable.just(data));
}
private Observable<Observable<byte[]>> createCharacteristicNotificationObservable(final UUID characteristicUuid,
NotificationSetupMode setupMode,
boolean isIndication) {
return setupCharacteristicNotification(characteristicUuid, setupMode, true, isIndication)
.flatMap(new Func1<Boolean, Observable<Boolean>>() {
@Override
public Observable<Boolean> call(Boolean onNext) {
return ObservableUtil.justOnNext(onNext);
}
})
.flatMap(new Func1<Boolean, Observable<? extends Observable<byte[]>>>() {
@Override
public Observable<? extends Observable<byte[]>> call(Boolean bluetoothGattDescriptorPair) {
if (!characteristicNotificationSources.containsKey(characteristicUuid)) {
return Observable.error(new IllegalStateException("Lack of notification source for given characteristic"));
}
return Observable.just(characteristicNotificationSources.get(characteristicUuid));
}
});
}
private void dismissCharacteristicNotification(UUID characteristicUuid, NotificationSetupMode setupMode, boolean isIndication) {
notificationObservableMap.remove(characteristicUuid);
setupCharacteristicNotification(characteristicUuid, setupMode, false, isIndication)
.subscribe(
Actions.empty(),
Actions.<Throwable>toAction1(Actions.empty())
);
}
@NonNull
private Observable<BluetoothGattDescriptor> getClientConfigurationDescriptor(UUID characteristicUuid) {
return getCharacteristic(characteristicUuid)
.map(new Func1<BluetoothGattCharacteristic, BluetoothGattDescriptor>() {
@Override
public BluetoothGattDescriptor call(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
BluetoothGattDescriptor bluetoothGattDescriptor =
bluetoothGattCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID);
if (bluetoothGattDescriptor == null) {
//adding notification descriptor if not present
bluetoothGattDescriptor = new BluetoothGattDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID, 0);
bluetoothGattCharacteristic.addDescriptor(bluetoothGattDescriptor);
}
return bluetoothGattDescriptor;
}
});
}
@NonNull
private Observable<byte[]> observeOnCharacteristicChangeCallbacks(UUID characteristicUuid) {
return characteristicNotificationSources.get(characteristicUuid);
}
@NonNull
private Observable<Boolean> setupCharacteristicNotification(
final UUID bluetoothGattCharacteristicUUID,
final NotificationSetupMode setupMode,
final boolean enabled,
final boolean isIndication
) {
if (setupMode == NotificationSetupMode.DEFAULT) {
final byte[] enableValue = isIndication ? ENABLE_INDICATION_VALUE : ENABLE_NOTIFICATION_VALUE;
return getClientConfigurationDescriptor(bluetoothGattCharacteristicUUID)
.flatMap(new Func1<BluetoothGattDescriptor, Observable<byte[]>>() {
@Override
public Observable<byte[]> call(BluetoothGattDescriptor bluetoothGattDescriptor) {
return writeDescriptor(bluetoothGattDescriptor, enabled ? enableValue : DISABLE_NOTIFICATION_VALUE);
}
})
.map(new Func1<byte[], Boolean>() {
@Override
public Boolean call(byte[] ignored) {
return true;
}
});
} else {
return just(true);
}
}
@Override
public <T> Observable<T> queue(RxBleRadioOperationCustom<T> operation) {
throw new UnsupportedOperationException("Mock does not support queuing custom operation.");
}
}